﻿using System;
using System.Collections.Generic;
using System.Linq;
using MathService.MathParser.ExpressionTrees.Nodes;
using MathService.Values;

namespace MathService.MathParser
{
    /// <summary>Парсер математических выражений</summary>
    public class ExpressionParser
    {
        public event EventHandler<EventArgs<ExpressionTreeNode>> NewNodeAdded;

        protected virtual void OnNewNodeAdded(EventArgs<ExpressionTreeNode> e)
        {
            NewNodeAdded.FastStart(this, e);
        }

        protected virtual void OnNewNodeAdded(ref ExpressionTreeNode NewNode)
        {
            ProcessNewNode(ref NewNode);

            var e = new EventArgs<ExpressionTreeNode>(NewNode);
            NewNodeAdded.FastStart(this, e);
            if(!ReferenceEquals(e.Argument, NewNode))
                NewNode = e.Argument;
        }

        protected void ProcessNewNode(ref ExpressionTreeNode NewNode)
        {
            var charNode = NewNode as CharNode;
            if(charNode != null)
                switch(charNode.Value)
                {
                    case '.':
                        if(NewNode.Parent is CharNode && ((CharNode)NewNode.Parent).Value == '.')
                        {
                            var value_node = NewNode[n => n.Parent].Last(n => !(n is OperatorNode) || n.Left == null);
                            NewNode["./."].Right = null;
                            var parent = value_node.Parent;
                            var interval_node = new IntervalNode(value_node);
                            if(parent != null)
                                parent.Right = interval_node;
                            NewNode = interval_node;
                        }
                        break;
                }
            else if(NewNode is OperatorNode)
            {

            }
        }

        /// <summary>Событие предобработки входящей строки</summary>
        public event EventHandler<EventArgs<string>> StringPreprocessing;

        /// <summary>Генерация события предобработки входящей строки</summary>
        /// <param name="args">Аргумент события, содержащий обрабатываемую строку</param>
        protected virtual void OnStringPreprocessing(EventArgs<string> args)
        {
            StringPreprocessing.FastStart(this, args);
        }

        /// <summary>Генерация события предобработки входящей строки</summary>
        /// <param name="StrExpression">Обрабатываемая строка</param>
        private void OnStringPreprocessing(ref string StrExpression)
        {
            var args = new EventArgs<string>(StrExpression);
            OnStringPreprocessing(args);
            StrExpression = args.Argument;
        }

        /// <summary>Аргумент события обнаружения функции</summary>
        public class FindFunctionEventArgs : EventArgs
        {
            /// <summary>Имя обнаруженой функции</summary>
            public string Name { get; private set; }
            /// <summary>Массив имён аргументов функции</summary>
            public string[] Arguments { get; private set; }

            public int ArgumentCount { get { return Arguments.Length; } }

            /// <summary>Делегат функции, который надо использовать при её вычислении</summary>
            public Delegate Function { get; set; }
            /// <summary>Инициализация аргумента события обнаружения функции</summary>
            /// <param name="Name">Имя функции</param>
            /// <param name="Arguments">Массив имён аргументов функции</param>
            public FindFunctionEventArgs(string Name, string[] Arguments)
            {
                this.Name = Name;
                this.Arguments = Arguments;
            }

            public bool SignatureEqual(string Name, int ArgumentsCount) { return this.Name == Name && ArgumentsCount == ArgumentCount; }
        }

        /// <summary>Событие, возникающее в процессе разбора математического выражения при обнаружении функции</summary>
        public event EventHandler<FindFunctionEventArgs> FindFunction;

        /// <summary>Обработчик события обнаружения функции в процессе разбора выражения</summary>
        /// <param name="Args">Аргументы события, содержащие имя функции, имена аргументов и делегат метода функции</param>
        protected virtual void OnFindFunction(FindFunctionEventArgs Args)
        {
            var handlers = FindFunction;
            if(handlers != null)
                handlers.FastStart(this, Args);
        }

        /// <summary>Обработчик события обнаружения функции в процессе разбора выражения</summary>
        /// <param name="Name">Имя функции</param>
        /// <param name="Arguments">Аргументы функции</param>
        /// <returns>Делегат функции</returns>
        private Delegate OnFunctionFind(string Name, string[] Arguments)
        {
            var args = new FindFunctionEventArgs(Name, Arguments);
            OnFindFunction(args);
            return args.Function;
        }

        /// <summary>Событие обработки переменных при разборе мат.выражений</summary>
        public event EventHandler<EventArgs<ExpressionVariabel>> VariableProcessing;

        /// <summary> Обработка обнаруженной переменной</summary>
        /// <param name="e">Обнаруженная переменная</param>
        protected virtual void OnVariableProcessing(EventArgs<ExpressionVariabel> e)
        {
            var handlers = VariableProcessing;
            if(handlers != null)
                handlers.Invoke(this, e);
        }

        /// <summary> Обработка обнаруженной переменной</summary>
        /// <param name="Variable">Обнаруженная переменная</param>
        private void OnVariableProcessing(ExpressionVariabel Variable)
        {
            OnVariableProcessing(new EventArgs<ExpressionVariabel>(Variable));
        }

        /// <summary>Множество запрещённых символов</summary>
        private readonly SetOf<char> f_ExcludeCharsSet = new SetOf<char>(" \r\n");

        /// <summary>Словарь констант</summary>
        private readonly Dictionary<string, double> f_Constans = new Dictionary<string, double>();

        /// <summary>Множество запрещённых симовлов</summary>
        public SetOf<char> ExcludeCharsSet { get { return f_ExcludeCharsSet; } }

        /// <summary>Разделитель выражений (по умолчанию ';')</summary>
        public char ExpressionSeparator { get; set; }

        /// <summary>Разделитель целой части и мантисы десятичного числа</summary>
        public char DecimalSeparator { get; set; }

        /// <summary>Константы</summary>
        public Dictionary<string, double> Constants { get { return f_Constans; } }

        /// <summary>Парсер математических выражений</summary>
        public ExpressionParser()
        {
            ExpressionSeparator = ',';
            DecimalSeparator = '.';

            #region Добавление стандартных констант

            f_Constans.Add("pi", Math.PI);
            f_Constans.Add("pi2", Consts.pi2);
            f_Constans.Add("pi05", Consts.pi05);
            f_Constans.Add("e", Math.E);
            f_Constans.Add("sqrt2", Consts.sqrt_2);
            f_Constans.Add("sqrt2inv", Consts.sqrt_2_inv);
            f_Constans.Add("sqrt3", Consts.sqrt_3);
            f_Constans.Add("sqrt5", Consts.sqrt_5);
            f_Constans.Add("ToDeg", Consts.Geometry.ToDeg);
            f_Constans.Add("ToRad", Consts.Geometry.ToRad);

            #endregion
        }

        /// <summary>Предварительная обработка входного строкового выражения</summary>
        /// <param name="Str">Обрабатываемая строка</param>
        protected virtual void StrPreprocessing(ref string Str)
        {
            // Удаление из строки всех символов, из множества запрещённых симоволов
            Str = new string(Str.Where(f_ExcludeCharsSet.NotContains).ToArray());
        }

        /// <summary>Разборать строку математического выражения</summary>
        /// <param name="StrExpression">Строковое представление математического выражения</param>
        /// <returns>Математическое выражение</returns>
        public MathExpression Parse(string StrExpression)
        {
            StrPreprocessing(ref StrExpression);
            OnStringPreprocessing(ref StrExpression);

            var expression = new MathExpression(StrExpression, this);

            ProcessVariables(expression);
            ProcessFunctions(expression);

            return expression;
        }

        /// <summary>Обработка переменных</summary>
        /// <param name="Expression">Обрабатываемое математическое выражение</param>
        internal void ProcessVariables(MathExpression Expression)
        {
            foreach(var variable in Expression.Variable.ToArray())
            {
                if(f_Constans.ContainsKey(variable.Name))
                {
                    Expression.Variable.MoveToConstCollection(variable);
                    variable.Value = f_Constans[variable.Name];
                }
                OnVariableProcessing(variable);
            }
        }

        /// <summary>Обработка функций</summary>
        /// <param name="Expression">Обрабатываемое математическое выражение</param>
        internal void ProcessFunctions(MathExpression Expression)
        {
            foreach(var function in Expression.Functions)
                switch(function.Name)
                {
                    case "Sin":
                    case "SIN":
                    case "sin":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Sin);
                        break;
                    case "COS":
                    case "Cos":
                    case "cos":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Cos);
                        break;
                    case "TAN":
                    case "Tan":
                    case "tan":
                    case "tn":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Tan);
                        break;
                    case "CTG":
                    case "Ctg":
                    case "ctg":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(x => 1 / Math.Tan(x));
                        break;
                    case "Sign":
                    case "sign":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(x => Math.Sign(x));
                        break;
                    case "Abs":
                    case "abs":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Abs);
                        break;
                    case "Exp":
                    case "EXP":
                    case "exp":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Exp);
                        break;
                    case "Sqrt":
                    case "SQRT":
                    case "√":
                    case "sqrt":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Sqrt);
                        break;
                    case "log10":
                    case "Log10":
                    case "LOG10":
                    case "lg":
                    case "Lg":
                    case "LG":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Log10);
                        break;
                    case "loge":
                    case "Loge":
                    case "LOGe":
                    case "ln":
                    case "Ln":
                    case "LN":
                        if(function.Arguments.Length != 1)
                            goto default;
                        function.Delegate = new Func<double, double>(Math.Log);
                        break;
                    case "log":
                    case "Log":
                    case "LOG":
                        if(function.Arguments.Length != 2)
                            goto default;
                        function.Delegate = new Func<double, double, double>(Math.Log);
                        break;
                    default:
                        var f = OnFunctionFind(function.Name, function.Arguments);
                        if(f == null)
                            throw new NotSupportedException(
                                String.Format("Обработка функции {0} не поддерживается", function.Name));
                        function.Delegate = f;
                        break;
                }
        }

        /// <summary>Метод определения узла дерева, реализующего оператор</summary>
        /// <param name="Name">Имя оператора</param>
        /// <returns>Узел дерева оператора</returns>
        public virtual ExpressionTreeNode GetOperatorNode(char Name)
        {
            switch(Name)
            {
                case '+':
                    return new AdditionOperatorNode();
                case '-':
                    return new SubstractionOperatorNode();
                case '*':
                case '×':
                case '·':
                    return new MultiplicationOperatorNode();
                case '/':
                    return new DivisionOperatorNode();
                case '^':
                    return new PowerOperatorNode();
                case '=':
                    return new EqualityOperatorNode();
                case '>':
                    return new GreaterThenOperatorNode();
                case '<':
                    return new LessThenOperatorNode();
                case '!':
                case '≠':
                    return new NotOperatorNode();
                case ':':
                    return new VariantOperatorNode();
                case '?':
                    return new SelectorOperatorNode();
                case '&':
                    return new AndOperatorNode();
                case '|':
                    return new OrOperatorNode();
                default:
                    return new CharNode(Name);
            }
        }

        public ComplexOperatorItem GetComplexOperator(string Name)
        {
            switch(Name)
            {
                case "summ":
                case "Summ":
                case "Σ":
                    return new SummOperator(Name);
                case "int":
                case "integral":
                case "Int":
                case "Integral":
                case "∫":
                    return new IntegralOperator(Name);
                default:
                    throw new NotSupportedException(String.Format("Функционал {0} не поддерживается", Name));
            }
        }

        /// <summary>Метод излвечения корня дерева из последовательности элементов математического выражения</summary>
        /// <param name="Group">группа элементов математического выражения</param>
        /// <param name="MathExpression">Ссылка на математическое выражение</param>
        /// <returns>Корень дерева мат.выражения</returns>
        internal ExpressionTreeNode GetRoot(Term[] Group, MathExpression MathExpression)
        {
            // Ссылка на последний обработанный узел дерева
            ExpressionTreeNode Last = null;
            for(var i = 0; i < Group.Length; i++) // в цикле по всем элементам группы
            {
                var node = Group[i].GetSubTree(this, MathExpression); // извлеч поддерево для текущего элемента группы
                // Если очередной элемент группы...
                if(Group[i] is NumberTerm) // ...очередной элемент число, то
                {
                    //...если есть впереди ещё два элемента и прошла удачная попытка считать разделитель дробного числи и мантиссу  
                    if(i + 2 < Group.Length && ((NumberTerm)Group[i]).TryAddFractionPart(ref node, Group[i + 1], DecimalSeparator, Group[i + 2]))
                        i += 2; //...то увеличить индекс текущей группы на два.
                }
                else if(Group[i] is BlockTerm) //...очередной элемент блок (со скобками)
                    node = new ComputedBracketNode( // очередной узел дерева - это вычислимый блок
                                new Bracket( //вид скобок:
                                            (((BlockTerm)Group[i]).OpenBracket), // копируем вид открывающей скобки
                                            ((BlockTerm)Group[i]).CloseBracket),  // копируем вид закрывающей скобки
                                node); //Внутри узел дерева

                //Проводим комбинацию текущего узла предыдущим узлом дерева
                Combine(Last, Last = node); // и назначаем текущий узел дерева предыдущим
                OnNewNodeAdded(ref Last);
            }

            // Если ссылка на предыдущий узел отсутствует, то это ошибка формата
            if(Last == null) throw new FormatException();
            return Last.Root; // вернуть корень дерева текущего элемента
        }

        /// <summary>Комбинация предыдущего и текущего узлов дерева</summary>
        /// <param name="Last">Предыдущий узел дерева (уже интегрированный в дерево)</param>
        /// <param name="Node">Текущий узел, который надо вставить в дерево</param>
        public virtual void Combine(ExpressionTreeNode Last, ExpressionTreeNode Node)
        {
            if(Last == null) return; // Если предыдущий узел дерева не указан, возврат

            if(Node is CharNode) // Если текущий узел - символьный узел, то
            {
                Last.LastRightChild = Node; // просто назначить его самым правым дочерним
                return;
            }


            var operator_node = Node as OperatorNode; // представляем текущий узел в виде узла-оператора
            if(operator_node != null)                 // если текущий узел является оператором...
            {
                //Пытаемся получить оператор предыдущего узла:
                // пытаемся привести предыдущий узел к типу узла оператора
                // либо пытаемся привести родительский узел предыдущего узла к типу узла оператора
                var parent_operator = Last as OperatorNode ?? Last.Parent as OperatorNode;

                if(parent_operator != null) // Если получена ссылка не предыдущий узел-оператор (и текущий является оператором)... то
                {
                    // Если левое поддерево предыдущего операторо пусто и родитель предыдущего оператора - тоже оператор
                    //      op <- устанавливаем предыдущим оператором родителя
                    //      |
                    //      op 
                    //     /  \
                    //  null   ?
                    if(parent_operator.Left == null && parent_operator.Parent is OperatorNode)
                        parent_operator = (OperatorNode)parent_operator.Parent;


                    if(parent_operator.Left == null)          // Если левое поддерево предыдущего оператора пусто...
                        operator_node.Left = parent_operator; //  устанавливаем предыдущий оператор в качестве левого поддерева текущего
                    else if(parent_operator.Right == null)    // Иначе если правое поддерево пусто
                        parent_operator.Right = Node;         //  установить текущий оператор правым поддеревом предыдущего
                    else                                      // Иначе если конфликт приоритетов
                    {
                        var priority = operator_node.Priority;  // извлекаем приоритет текущего узла
                        // Если приоритет текущего оператора меньше, либо равен приоритету предыдущего
                        if(priority <= parent_operator.Priority)
                        {
                            // то надо подниматься вверх под дереву до тех пор
                            parent_operator = (OperatorNode)parent_operator.Parents
                                // пока встречаемые на пути операторы имеют приоритет выше приоритета текущего оператора
                                        .TakeWhile(n => n is OperatorNode && priority <= ((OperatorNode)n).Priority)
                                // взять последний из последовательности
                                        .LastOrDefault() ?? parent_operator; // если вернулась пустая ссылка, то взять предыдущий оператор

                            // На текущий момент предыдущий оператор имеет приоритет выше приоритета текущего оператора

                            if(parent_operator.IsRoot) // Если предыдущий оператор - корень дерева
                                // Если приоритет оператора в корне дерева больше, либо равен приоритету текущего оператора
                                if(priority <= parent_operator.Priority) // todo: проверить необходимость условия
                                    // Присвоить левому поддереву текущего оператора предыдущий
                                    operator_node.Left = parent_operator;
                                // todo: проверить достижимость следующего блока
                                else // если оператор в корне дерева имеет меньший приоритет, чем приоритет текущего оператора
                                {    //  то вставить текущий оператор в правое поддерево родителя предыдущего оператора
                                    throw new NotImplementedException("!!!");
                                    //var right = parent_operator.Right; // сохранить правое поддерево предыдущего оператора
                                    //parent_operator.Right = Node; // У предыдущего оператора в правое поддерево внести текущий оператор
                                    //operator_node.Left = right;   // В левое поддерево текущего оператора внести сохранённое правое поддерево
                                }
                            else // Иначе если предыдущий оператор не корень
                            {
                                var parent = parent_operator.Parent; // сохранить ссылку на родителя предыдущего оператора
                                parent.Right = Node;                 // записать текущий оператор в качестве правого поддерева
                                operator_node.Left = parent_operator;// записать предыдущий оператора левым поддеревом текущего
                            }
                        }
                        else //если приоритет текущего оператора больше приоритета предыдущего
                        {
                            // то надо спускаться в правое поддерево до тех пор
                            parent_operator = (OperatorNode)parent_operator.RightNodes
                                // пока встречаемые на пути операторы имеют левые поддеревья и приоритет операторов меньше текущего
                                        .TakeWhile(n => n is OperatorNode && n.Left != null && ((OperatorNode)n).Priority < priority)
                                // взять последний из последовательности
                                        .LastOrDefault() ?? parent_operator;  // если вернулась пустая ссылка, то взять предыдущий оператор

                            // На текущий момент предыдущий оператор имеет приоритет ниже приоритета текущего оператора

                            var right = parent_operator.Right; // сохранить правое поддерево предыдущего оператора
                            parent_operator.Right = Node;      // в правое поддерево предыдущего оператора попадает текущий
                            operator_node.Left = right;        // в левое поддерево текущего оператора записывается сохранённое правое 
                        }
                    }
                }
                else // Если предыдущий узел не является оператором
                    operator_node.Left = Last; // записать предыдущий узел левым поддеревом текущего
                return; // возврат
            }
            // Если текущий узел оператором не является

            if(Last is OperatorNode) // если предыдущий узел является оператором
            {
                Last.Right = Node; // добавыить текуий в правое поддерево предыдущего
                return;            // возврат
            }
            // Если ни текущий не придыдущий узлы не являются операторами

            //Если предыдущий узел был числом, или предыдущий узел был скобками и текущий - скобки
            if(Last is ConstValueNode || (Last is ComputedBracketNode && Node is ComputedBracketNode))
            {
                //Сохряняем ссылку на родителя предыдущего узла
                var parent = Last.Parent;
                if(parent != null) // если родитель есть
                    // в правое поддерево родителя записываем оператор перемножения предыдущего узла и текущего
                    parent.Right = new MultiplicationOperatorNode(Last, Node);
                else // если предыдущий узел был узлом дерева
                    // создаём новый узел-оператор умножения предыдущего узла и текущего, который становится новым корнем дерева
                    new MultiplicationOperatorNode(Last, Node);
                return; // возврат.
            }

            Last.Right = Node;
            //throw new FormatException(); // Если не сработало ни одно условие, то это ошибка формата
        }
    }
}